package com.github.zzay.user.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.zzay.common.core.utils.RedisCache;
import com.github.zzay.common.security.utils.JwtUtils;
import com.github.zzay.user.entity.User;
import com.github.zzay.user.dto.UserDto;
import com.github.zzay.user.mapper.UserMapper;
import com.github.zzay.user.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.*;

/**
 * @author zzay
 * @className UserServiceImpl
 * @description User Service Implementation
 * @create 2022/04/30 23:29
 */
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    /**
     * User Mapper
     */
    @Autowired
    private UserMapper userMapper;

    /**
     * Authentication manager
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * Redis cache
     */
    @Autowired
    private RedisCache redisCache;

    /**
     * Register
     *
     * @param userDto User DTO
     * @return Operation result
     */
    @Override
    public Boolean register(UserDto userDto) {
        // get username and password
        String username = userDto.getUsername();
        String password = userDto.getPassword();
        // check whether the username has been user or not
        User user = userMapper.selectByUsername(username);
        if (user != null) {
            return false;
        }
        // create a new user record and insert it
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("ROLE_user"));
        authorities.add(new SimpleGrantedAuthority("system:user"));
        User newUser = new User(username, new BCryptPasswordEncoder().encode(password), authorities);
        log.info("======== Register: " + username + "-" + password + " ========");
        return this.save(newUser);
    }

    /**
     * Log-in
     *
     * @param userDto User DTO
     * @return Operation result
     */
    @Override
    public Map<String, String> login(UserDto userDto) {
        // Construct the authentication token using the input principal and credential
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                userDto.getUsername(), userDto.getPassword());
        // Authenticate the input user information
        Authentication authentication = authenticationManager.authenticate(authenticationToken);
        if (Objects.isNull(authentication)) {
            throw new RuntimeException("Failed to log in !");
        }
        // If pass authentication, generate JWT with the principal's ID
        User user = (User) authentication.getPrincipal();
        String userId = user.getId().toString();
        String jwt = JwtUtils.createJWT(userId);
        // Store the JWT for some time in redis
        redisCache.setCacheObject("login:" + userId, user);
        // Return JWT to controller to respond
        Map<String, String> data = new HashMap<>();
        data.put("token", jwt);
        return data;
    }

    /**
     * Log-out
     *
     * @param userDto User DTO
     * @return Operation result
     */
    @Override
    public Boolean logout(UserDto userDto) {
        // Get authentication token from the context
        UsernamePasswordAuthenticationToken authenticationToken =
                (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
        // Get the matched user's information with the token
        User user = (User) authenticationToken.getPrincipal();
        Long userId = user.getId();
        // Delete records in Redis cache
        redisCache.deleteObject("login:" + userId);
        return true;
    }

}
